/*
 * IM - Image I/O Library
 * Copyright (c) 1996 CENPES/Petrobras, TeCGraf/PUC-Rio.
 * Antonio E. Scuri - scuri@tecgraf.puc-rio.br
 * 
 * IMTIF.C
 * 
 * Contains the implementation of the TIF file format functions.
 *
 * History:
 *          1996.04.05 - Changed the structure of the module to the new format.
 *          1996.06.14 - Finished inicial version.
 *
 */

#include "im.h"
#include "imio.h"
#include "imtif.h"
#include "binfile.h"

#include <stdlib.h>
#include <memory.h>

#include "tiffio.h"
#include "tiffiop.h"


/* arquivos gerados ou modificados da libtiff para a IM */
/*   tif_jpeg.c   */
/*   g3_states.c  (mkg3states.c) */
/*   port.h       */
/*   version.h  (mkversion.c) */

/* libtiff IO handlers */

static tsize_t imTIFReadProc(thandle_t fd, tdata_t buf, tsize_t size)
{
  binFile* file_bin = (binFile*)fd;

  if (file_bin->WriteFlag) return -1;

  binFileRead1ByteBuf(file_bin, buf, size);
  if (binFileError(file_bin))
    return -1;
  else
    return size;
}

static tsize_t imTIFWriteProc(thandle_t fd, tdata_t buf, tsize_t size)
{
  binFile* file_bin = (binFile*)fd;

  if (!file_bin->WriteFlag) return -1;

  binFileWrite1ByteBuf(file_bin, buf, size);
  if (binFileError(file_bin))
    return -1;
  else
    return size;
}

static toff_t imTIFSeekProc(thandle_t fd, toff_t off, int whence)
{
  binFile* file_bin = (binFile*)fd;
  switch (whence)
  {
  case SEEK_SET:
    binFileSeekTo(file_bin, off);
    break;
  case SEEK_CUR:
    binFileSeekOffset(file_bin, off);
    break;
  case SEEK_END: 
    binFileSeekFrom(file_bin, off);
    break;
  }

  if (binFileError(file_bin))
    return -1;
  else
    return binFileTell(file_bin);
}

static int imTIFCloseProc(thandle_t fd)
{
  binFile* file_bin = (binFile*)fd;
  binFileClose(file_bin);
  return 0;
}

static toff_t imTIFSizeProc(thandle_t fd)
{
  binFile* file_bin = (binFile*)fd;
  return binFileSize(file_bin);
}

static int imTIFMapProc(thandle_t fd, tdata_t* pbase, toff_t* psize)
{
  (void) fd; (void) pbase; (void) psize;
  return (0);
}

static void imTIFUnmapProc(thandle_t fd, tdata_t base, toff_t size)
{
  (void) fd; (void) base; (void) size;
}

TIFF* TIFFFdOpen(int fd, const char* name, const char* mode)
{
  TIFF* tif;

  tif = TIFFClientOpen(name, mode, (thandle_t) fd,  imTIFReadProc, imTIFWriteProc,
                                                    imTIFSeekProc, imTIFCloseProc, 
                                                    imTIFSizeProc, imTIFMapProc, 
                                                    imTIFUnmapProc);
  if (tif)
    tif->tif_fd = fd;

  return (tif);
}

TIFF* TIFFOpen(const char* name, const char* mode)
{
  static const char module[] = "TIFFOpen";
  int fd;

  if (mode[0] == 'r')
    fd = (int)binFileOpen((char*)name, 0, 0);
  else
    fd = (int)binFileOpen((char*)name, 1, 0);

  if (fd == 0)
    return NULL;
  
  return (TIFFFdOpen(fd, name, mode));
}

void* _TIFFmalloc(tsize_t s)
{
  return (malloc((size_t) s));
}

void _TIFFfree(tdata_t p)
{
  free(p);
}

void* _TIFFrealloc(tdata_t p, tsize_t s)
{
  return (realloc(p, (size_t) s));
}

void _TIFFmemset(tdata_t p, int v, tsize_t c)
{
  memset(p, v, (size_t) c);
}

void _TIFFmemcpy(tdata_t d, const tdata_t s, tsize_t c)
{
  memcpy(d, s, (size_t) c);
}

int _TIFFmemcmp(const tdata_t p1, const tdata_t p2, tsize_t c)
{
  return (memcmp(p1, p2, (size_t) c));
}

TIFFErrorHandler _TIFFwarningHandler = NULL;
TIFFErrorHandler _TIFFerrorHandler = NULL;


typedef struct _imFileTIF
{
  unsigned long line_size;    /* line buffer size */
  unsigned char* line_buffer; /* line buffer */
  TIFF* tiff;
  uint16 Photometric;
} imFileTIF;

static imTiffImageDesc image_desc_cb = NULL;

static imFile* imTIFReadOpen(char* filename, int *err)
{
  uint32 Width, Height;
  uint16 config, SPP = 1, BPS = 1, SampleFormat = SAMPLEFORMAT_UINT;
  uint16 Compression = COMPRESSION_NONE;
  imFile *ifile = (imFile*)malloc(sizeof(imFile));
  imFileTIF *file_tif = (imFileTIF*)malloc(sizeof(imFileTIF));
  TIFF *tiff;

  tiff = TIFFOpen(filename, "r");
  if (tiff == NULL)
  {
    free(file_tif);
    free(ifile);
    if (err) *err = IM_ERR_FORMAT;
    return NULL;
  }

  file_tif->tiff = tiff;
  ifile->file = file_tif;

  TIFFGetField(tiff, TIFFTAG_COMPRESSION, &Compression);
  ifile->compress = (Compression == COMPRESSION_NONE)? IM_NONE: (Compression == COMPRESSION_PACKBITS)? IM_DEFAULT: IM_COMPRESSED;

  TIFFGetField(tiff, TIFFTAG_IMAGEWIDTH, &Width);
  TIFFGetField(tiff, TIFFTAG_IMAGELENGTH, &Height);
  TIFFGetFieldDefaulted(tiff, TIFFTAG_BITSPERSAMPLE, &BPS);
  TIFFGetFieldDefaulted(tiff, TIFFTAG_SAMPLESPERPIXEL, &SPP);
  TIFFGetField(tiff, TIFFTAG_PHOTOMETRIC, &file_tif->Photometric);
  TIFFGetFieldDefaulted(tiff, TIFFTAG_SAMPLEFORMAT, &SampleFormat);
  TIFFGetField(tiff, TIFFTAG_PLANARCONFIG, &config);

  if (imResolutionCB)
  {
    double xres = 0, yres = 0;
    int res_unit = IM_RES_NONE;
    float tiff_xres, tiff_yres;
    unsigned short tiff_res_unit;

    TIFFGetFieldDefaulted(tiff, TIFFTAG_RESOLUTIONUNIT, &tiff_res_unit);

    if (tiff_res_unit != RESUNIT_NONE)
    {
      TIFFGetField(tiff, TIFFTAG_XRESOLUTION, &tiff_xres);
      TIFFGetField(tiff, TIFFTAG_YRESOLUTION, &tiff_yres);
      xres = tiff_xres;
      yres = tiff_yres;

      if (tiff_res_unit == RESUNIT_INCH)
        res_unit = IM_RES_DPI;
      else
        res_unit = IM_RES_DPC;
    }

    imResolutionCB(filename, &xres, &yres, &res_unit);
  }

  if (image_desc_cb)
  {
    char* image_desc = NULL;
    TIFFGetField(tiff, TIFFTAG_IMAGEDESCRIPTION, &image_desc);
    if (image_desc)
      image_desc_cb(filename, image_desc);
  }

  ifile->width = Width;
  ifile->height = Height;

  if (SampleFormat != SAMPLEFORMAT_UINT || config != PLANARCONFIG_CONTIG)
  {
    file_tif->line_buffer = NULL;
    if (err) *err = IM_ERR_TYPE;
    return ifile;
  }

  ifile->type = IM_MAP;

  if (BPS == 1 && SPP == 1 && (file_tif->Photometric == PHOTOMETRIC_MINISBLACK || file_tif->Photometric == PHOTOMETRIC_MINISWHITE))
    {
    ifile->pal_size = 2;
    file_tif->line_size = (Width + 7) / 8;
    }
  else if (BPS == 4 && SPP == 1 && (file_tif->Photometric == PHOTOMETRIC_PALETTE || file_tif->Photometric == PHOTOMETRIC_MINISBLACK || file_tif->Photometric == PHOTOMETRIC_MINISWHITE))
    {
    ifile->pal_size = 16;
    file_tif->line_size = (Width + 1) / 2;
    }
  else if (BPS == 8 && SPP == 1 && (file_tif->Photometric == PHOTOMETRIC_PALETTE || file_tif->Photometric == PHOTOMETRIC_MINISBLACK || file_tif->Photometric == PHOTOMETRIC_MINISWHITE))
    {
    ifile->pal_size = 256;
    file_tif->line_size = Width;
    }
  else if (BPS == 8 && SPP == 3 && file_tif->Photometric == PHOTOMETRIC_RGB)
    {
    ifile->type = IM_RGB;
    ifile->pal_size = 0;
    file_tif->line_size = 3 * Width;
    }
  else
  {
    file_tif->line_buffer = NULL;
    if (err) *err = IM_ERR_TYPE;
    return ifile;
  }

  /* now initialize the imFile control data */

  ifile->split = 0;     /* image is RGB packed */
  ifile->planes[0] = 0; /* sets the default plane order since split = 0 */
  ifile->planes[1] = 1;
  ifile->planes[2] = 2;
  ifile->cur_line = ifile->height - 1;  /* first line */
  ifile->pre_colors = 1;  /* color map is before the image */

  file_tif->line_buffer = (unsigned char*)malloc(file_tif->line_size);

  if (err) *err = IM_ERR_NONE;

  return ifile;
}


static imFile* imTIFWriteOpen(char* filename, int width, int height, int type, int compress, int pal_size, int *err)
{
  uint32 Width = width, Height = height;
  uint16 SPP = (uint16)((type == IM_RGB)? 3: 1), BPS = 8;
  uint16 Compression = (uint16)(compress==IM_DEFAULT? COMPRESSION_PACKBITS: compress==IM_COMPRESSED? COMPRESSION_LZW: COMPRESSION_NONE);
  uint32 RPS = (uint32) -1;
  imFile *ifile = (imFile*)malloc(sizeof(imFile));
  imFileTIF *file_tif = (imFileTIF*)malloc(sizeof(imFileTIF));
  TIFF *tiff;

  tiff = TIFFOpen(filename, "w");
  if (tiff == NULL)
  {
    free(ifile);
    if (err) *err = IM_ERR_OPEN;
    return NULL;
  }

  file_tif->tiff = tiff;

  file_tif->Photometric = (uint16)((type == IM_RGB)? PHOTOMETRIC_RGB: PHOTOMETRIC_PALETTE);

  TIFFSetField(tiff, TIFFTAG_IMAGEWIDTH, Width);
  TIFFSetField(tiff, TIFFTAG_IMAGELENGTH, Height);
  TIFFSetField(tiff, TIFFTAG_BITSPERSAMPLE, BPS);
  TIFFSetField(tiff, TIFFTAG_SAMPLESPERPIXEL, SPP);
  TIFFSetField(tiff, TIFFTAG_PHOTOMETRIC, file_tif->Photometric);
  TIFFSetField(tiff, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG);
  TIFFSetField(tiff, TIFFTAG_COMPRESSION, Compression);

  RPS = TIFFDefaultStripSize(tiff, RPS);
  TIFFSetField(tiff, TIFFTAG_ROWSPERSTRIP, RPS);

  if (imResolutionCB)
  {
    double xres = 0, yres = 0;
    int res_unit = IM_RES_NONE;

    imResolutionCB(filename, &xres, &yres, &res_unit);

    if (res_unit != IM_RES_NONE)
    {
      float tiff_xres, tiff_yres;
      unsigned short tiff_res_unit;

      if (res_unit == IM_RES_DPI)
        tiff_res_unit = RESUNIT_INCH;
      else
        tiff_res_unit = RESUNIT_CENTIMETER;

      TIFFSetField(tiff, TIFFTAG_RESOLUTIONUNIT, tiff_res_unit);

      tiff_xres = (float)xres;
      tiff_yres = (float)yres;

      TIFFSetField(tiff, TIFFTAG_XRESOLUTION, tiff_xres);
      TIFFSetField(tiff, TIFFTAG_YRESOLUTION, tiff_yres);
    }
  }

  if (image_desc_cb)
  {
    char image_desc[512];
    image_desc_cb(filename, image_desc);
    TIFFSetField(tiff, TIFFTAG_IMAGEDESCRIPTION, image_desc);
  }

  ifile->file = file_tif;

  ifile->width = width;
  ifile->height = height;
  ifile->type = type;
  ifile->pal_size = (type == IM_RGB)? 0: pal_size;
  ifile->compress = compress;

  /* now initialize the imFile control data */

  ifile->split = 0;     /* image is RGB packed */
  ifile->planes[0] = 0; /* sets the default plane order since split = 0 */
  ifile->planes[1] = 1;
  ifile->planes[2] = 2;
  ifile->cur_line = ifile->height - 1;  /* first line */
  ifile->pre_colors = 1;  /* color map is before the image */

  if (ifile->type == IM_RGB)
  {
    file_tif->line_size = width * 3;
    file_tif->line_buffer = (unsigned char*)malloc(file_tif->line_size);
  }
  else
  {
    file_tif->line_size = 0;
    file_tif->line_buffer = NULL;
  }

  if (err) *err = IM_ERR_NONE;

  return ifile;
}


static void imTIFClose(imFile* ifile)
{
  imFileTIF *file_tif = (imFileTIF*)ifile->file;

  TIFFClose(file_tif->tiff);

  if (file_tif->line_buffer) free(file_tif->line_buffer);
  free(file_tif);
  free(ifile);
}


static int imTIFReadColors(imFile* ifile, long *colors)
{
  int c;
  imFileTIF *file_tif = (imFileTIF*)ifile->file;
  TIFF* tiff = file_tif->tiff;

  if (file_tif->Photometric == PHOTOMETRIC_MINISBLACK || file_tif->Photometric == PHOTOMETRIC_MINISWHITE)
  {
    unsigned char Value;
    double Norm = 255.0 / (ifile->pal_size - 1.0);

    for (c = 0; c < ifile->pal_size; c++)
    {
      Value = (unsigned char)(Norm * (file_tif->Photometric == PHOTOMETRIC_MINISBLACK? c: (ifile->pal_size - 1) - c));
      colors[c] = imEncodeColor(Value, Value, Value);
    }
  }
  else
  {
    uint16 *rmap, *gmap, *bmap;
    TIFFGetField(tiff, TIFFTAG_COLORMAP, &rmap, &gmap, &bmap);

    for (c = 0; c < ifile->pal_size; c++)
      colors[c] = imEncodeColor((unsigned char)(rmap[c] >> 8), (unsigned char)(gmap[c] >> 8), (unsigned char)(bmap[c] >> 8));
  }

  return IM_ERR_NONE;
}

static int IsGrayPalette(long *colors, int pal_size)
{
  unsigned char Red, Green, Blue;
  int c;
  for (c = 0; c < pal_size; c++)
  {
    imDecodeColor(&Red, &Green, &Blue, *colors++);

    if (Red != Green || Green != Blue)
      return 0;
  }

  return 1;
}

static int imTIFWriteColors(imFile* ifile, long *colors)
{
  int c;
  imFileTIF *file_tif = (imFileTIF*)ifile->file;
  TIFF* tiff = file_tif->tiff;
  unsigned char Red, Green, Blue;
  uint16 rmap[256], gmap[256], bmap[256];

  if (IsGrayPalette(colors, ifile->pal_size))
  {
    file_tif->Photometric = PHOTOMETRIC_MINISBLACK;
    TIFFSetField(tiff, TIFFTAG_PHOTOMETRIC, file_tif->Photometric);
    return IM_ERR_NONE;
  }

  memset(rmap, 0, 256 * 2);
  memset(gmap, 0, 256 * 2);
  memset(bmap, 0, 256 * 2);

  for (c = 0; c < ifile->pal_size; c++)
  {
    imDecodeColor(&Red, &Green, &Blue, *colors++);
    rmap[c] = (uint16)(((uint16)Red) << 8);
    gmap[c] = (uint16)(((uint16)Green) << 8);
    bmap[c] = (uint16)(((uint16)Blue) << 8);
  }

  TIFFSetField(tiff, TIFFTAG_COLORMAP, rmap, gmap, bmap);

  return IM_ERR_NONE;
}


static int imTIFReadLine(imFile* ifile, unsigned char *map)
{
  int j;
  imFileTIF *file_tif = (imFileTIF*)ifile->file;
  TIFF* tiff = file_tif->tiff;

  if (TIFFReadScanline(file_tif->tiff, file_tif->line_buffer, (ifile->height - 1) - ifile->cur_line, 0) <= 0)
    return IM_ERR_READ;

  switch (ifile->pal_size)
    {
    case 2:
      for (j = 0; j < ifile->width; j++)
        map[j] = (unsigned char)((file_tif->line_buffer[j / 8] >> (7 - j % 8)) & 0x01);
      break;
    case 16:
      for (j = 0; j < ifile->width; j++)
        map[j] = (unsigned char)((file_tif->line_buffer[j / 2] >> ((1 - j % 2) * 4)) & 0x0F);
      break;
    case 256:
      memcpy(map, file_tif->line_buffer, ifile->width);
      break;
    }

  /* we have just read one more line */
  ifile->cur_line--;

  return IM_ERR_NONE;
}


static int imTIFWriteLine(imFile* ifile, unsigned char *map)
{
  imFileTIF *file_tif = (imFileTIF*)ifile->file;
  TIFF* tiff = file_tif->tiff;

  if (TIFFWriteScanline(tiff, map, (ifile->height - 1) - ifile->cur_line, 0) < 0)
    return IM_ERR_WRITE;

  /* we have just write one more line */
  ifile->cur_line--;

  return IM_ERR_NONE;
}


static int imTIFReadLineRGB(imFile* ifile, unsigned char *red, unsigned char *green, unsigned char *blue)
{
  int xpos, j;
  imFileTIF *file_tif = (imFileTIF*)ifile->file;
  TIFF* tiff = file_tif->tiff;

  if (TIFFReadScanline(tiff, file_tif->line_buffer, (ifile->height - 1) - ifile->cur_line, 0) <= 0)
    return IM_ERR_READ;

  for (j = 0; j < ifile->width; j++)
  {
    xpos = j * 3;
    red[j] = file_tif->line_buffer[xpos];
    green[j] = file_tif->line_buffer[xpos + 1];
    blue[j] = file_tif->line_buffer[xpos + 2];
  }

  /* we have just read one more line */
  ifile->cur_line--;

  return IM_ERR_NONE;
}


static int imTIFWriteLineRGB(imFile* ifile, unsigned char *red, unsigned char *green, unsigned char *blue)
{
  int xpos, j;
  imFileTIF *file_tif = (imFileTIF*)ifile->file;
  TIFF* tiff = file_tif->tiff;

  for (j = 0; j < ifile->width; j++)
  {
    xpos = j * 3;
    file_tif->line_buffer[xpos] = red[j];
    file_tif->line_buffer[xpos + 1] = green[j];
    file_tif->line_buffer[xpos + 2] = blue[j];
  }

  if (TIFFWriteScanline(tiff, file_tif->line_buffer, (ifile->height - 1) - ifile->cur_line, 0) < 0)
    return IM_ERR_WRITE;

  /* we have just write one more line */
  ifile->cur_line--;

  return IM_ERR_NONE;
}


static int imTIFRegisterCallback(imCallback cb, int id)
{
  switch(id)
  {
  case IM_TIF_IMAGE_DESCRIPTION_CB:
    {
      image_desc_cb = (imTiffImageDesc)cb;
      return 1;
    }
  }

  /* callback not found */
  return 0;
}


static imFormat _imFormatTIF =
{
  0,
  imTIFRegisterCallback,
  imTIFReadOpen,
  imTIFWriteOpen,
  imTIFClose,
  imTIFReadColors,
  imTIFWriteColors,
  imTIFReadLine,
  imTIFWriteLine,
  imTIFReadLineRGB,
  imTIFWriteLineRGB
};

imFormat* imFormatTIF(void)
{
  return &_imFormatTIF;
}



